<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>继承的方法</title>
</head>
<body>
    <script>
        // 原型链继承
        /*
            核心： 子类原型等于父类实例
            缺点： 多个实例对引用类型的操作会被覆盖
            原理： 改变了原型链的指向
        */
        // 父类
        function Person() {
            this.colors = ['black', 'white']
        }
        // 子类
        function Child() {

        }
        Child.prototype = new Person()
        // 我们知道原型对象身上有个 constructor 属性， 用于指向构造函数， 这样我们是修改了构造函数中的 constructor 的指向
        // 如果我们不改回去， Child.prototype.constructor 就已经不指向 Child 了
        Child.prototype.constructor = Child
        const child = new Child()
        console.log('🚀 ~ This is a result of console.log ~ ✨: ', child.colors);
        // 但原型链继承有缺陷， 多个实例对引用类型的操作会被覆盖， 原因： new Person（） 在 Child 的原型链上， 是同一份数据， 修改了就等于修改了所有实例
        child.colors.push('red')
        console.log('🚀 ~ This is a result of console.log ~ ✨:push.red ', child.colors);
        const instance = new Child()
        console.log('🚀 ~ This is a result of console.log ~ ✨:instance  ', instance.colors);


        // 构造函数继承
        /*
            核心： 使用 call 等方法， 显示改变父类的 this 指向， 使父类的 this 指向子类
            缺点： 只能继承父类的实例属性和方法， 不能继承原型属性和方法，影响性能， 每个子类都有父类实例函数的副本， 影响性能
            原理： 在子类构造函数中， 调用父类构造函数并显示的改变父类构造函数的 this 指向子类
        */

        function ConstructorPerson() {
            this.colors = ['black', 'white']
        }

        function ConstructorChild() {
            ConstructorPerson.call(this) // 这句话是核心  
        }

        const constructorChild = new ConstructorChild()
        constructorChild.colors.push('red')
        console.log('🚀 ~ This is a result of console.log ~ ✨:push.red ', constructorChild);
        const instanceConstructor = new ConstructorChild()
        console.log('instanceConstructor  ', instanceConstructor);


        // 组合继承
        /*
            核心： 在原型链上继承了一份， 在实例身上也继承了一份。 构造函数继承和原型链继承的组合, 将构造函数和原型链继承结合， 解决了原型链继承的不足， 既可以继承实例属性， 也可以继承原型属性
            缺点： 需要维护两份相同的数据， 在实例身上有一份， 原型身上还有一份
            原理： 增强子类实例，在实例身上有一份， 在原型链上再写一份， 可以弥补构造函数继承不能访问原型链上的属性， 也可以解决原型链继承时多个实例对象对引用类型数据操作时的覆盖， 因为他会先找实例身上， 再找原型身上
        */

        function CompositePerson() {
            this.colors = ['black', 'white']
            this.name = 'parent'
        }

        CompositePerson.prototype.getName = function () {
            return this.name
        }

        function CompositeChild() {
            CompositePerson.call(this) // 构造函数继承， 给实例身上添加了两个属性
            this.name = 'child' // 写在这里
        }

        // 继承原型链
        CompositeChild.prototype = new CompositePerson() // 原型链继承， 在原型身上添加了两个属性
        CompositeChild.prototype.constructor = CompositeChild // 在查找时依然以 CompositeChild 为构造函数
        const compositeChild = new CompositeChild()

        console.log('🚀 ~ This is a result of console.log ~ ✨: ', compositeChild.colors, compositeChild.name);
        compositeChild.colors.push('red')   
        console.log('��� ~ This is a result of console.log ~ ��:push.red ', compositeChild.colors);
        const compositeChildInstance = new CompositeChild()
        console.log('compositeChildInstance  ', compositeChildInstance);

        // 原型式继承
        /*
            核心： 利用一个空对象作为中介， 将某个对象直接赋值给空对象构造函数的原型
            缺点： 原型链继承多个实例的引用类型属性指向相同， 如果对引用类型数据操作， 会覆盖
            原理： 利用对象原型进行继承， 以一个对象作为原型， 实现继承
        */
        const proto = {
            name: 'proto',
            colors: ['black', 'white']
        }
        // 以这对象作为原型， 实现继承
        const instanceProto = Object.create(proto) // 实现了原型的继承
        instanceProto.name = 'instanceProto'
        instanceProto.colors.push('red')
        console.log(instanceProto);
        // colors 的值会被上一步覆盖
        const instanceProto2 = Object.create(proto)
        console.log(instanceProto2, 'instanceProto2');

        // 寄生式继承
        /*
            核心： 在原型式继承的基础上， 增强对象， 返回构造函数
            缺点： 和原型式继承相同，继承了原型链的多个实例的引用类型属性指向相同， 如果对引用类型数据操作， 会覆盖
            原理： 使用 Object.create() 创建对象后， 在这对象身上添加方法或属性， 最后返回这个对象
        */
        function createAdditionalObj (obj) {
            // 用传进来的对象作为原型
            const temp = Object.create(obj)
            temp.sayHi = function () { // 实现寄生
                console.log('hi');
            }
            // 返回这个对象
            return temp
        }

        const additionalObj = createAdditionalObj(proto) // 如果多个对象对引用类型数据操作， 会覆盖， 后面创建的对象， 也会对应的被覆盖
        additionalObj.sayHi()
        console.log('🚀 ~ This is a result of console.log ~ ✨: ', additionalObj);

        // 寄生组合式继承
        /*
            核心： 使用组合继承方式（原型链继承 + 构造函数继承） + 寄生式继承的方式实现
            缺点： 实现有复杂性， 对于新手来说， 可能不容易理解， 并且原型链有污染的风险， 如果修改过父类的原型， 那么整个原型链都会被污染， 有性能开销， 虽然只调用一次父类构造函数， 但是依然涉及创建父类的副本以及修改子类的原型
            会造成性能消耗
            原理： 原型链继承 + 构造函数继承 + 寄生式继承
            注意： 这是一种比较成熟的实现方式， 很多库都在使用这种方法
        */

        function inheritPrototype (child, parent) {
            // 我们用父类的原型对象构造一个对象的副本
            const prototype = Object.create(parent.prototype) // 可以拿到父类的原型对象
            prototype.constructor = child // 修改 constructor 指向子类
            // 原型链继承， 将父类的实例对象赋值给子类的原型对象
            child.prototype = prototype
        }

        // 父类
        function combineProtoPerson () {
            this.colors = ['black', 'white']
            this.name = 'parent'
        }

        // 子类
        function combineProtoChild () {
            combineProtoPerson.call(this) // 构造函数继承
            this.name = 'child'
        }

        // 实现寄生式继承, 用一个函数实现
        inheritPrototype(combineProtoChild, combineProtoPerson)

        const instance1 = new combineProtoChild()
        const instance2 = new combineProtoChild()

        instance1.colors = ['black', 'white']
        instance1.colors.push('red')
        console.log('🚀 ~ This is a result of console.log ~ ✨: ', instance1.colors);
        console.log(instance2);

        // Es6 的 class 语法实现继承
        class ParentClass {
            constructor (colors, name) {
                this.colors = colors
                this.name = name
            }

            get() {
                console.log('hello');
            }
        }

        class ChildClass extends ParentClass {
            constructor (colors, name) {
                super(colors, name)
                this.name = 'child1'
            }
        }

        const childClass = new ChildClass(['black', 'white'], 'child')
        console.log('🚀 ~ This is a result of console.log ~ ✨: ', childClass);

        // 混入方式继承多个对象
        /*
            核心： 使用 Object.assign() 方法， 将多个对象原型身上的方法拷贝到目标对象的原型上
            缺点： 存在原型污染， 如果出现同名的属性或方法， Object.assign 方法后面对象的会覆盖前面的， 会造成原型污染， 代码可读性性差
            原理： 利用 Object.assign() 方法， 将多个对象原型身上的方法拷贝到目标对象的原型上
        */

         function MyParent() {
             this.colors = ['black', 'white']
             this.name = 'parent'
         }

        MyParent.prototype.get = function () {
            console.log('hello');
        }

         function MyChild() {
             this.name = 'child'
         }

         function TargetClass() {
            // 给实例添加属性
            MyParent.call(this)
            MyChild.call(this)
         }

         TargetClass.prototype = Object.create(MyParent.prototype) // 以父类原型对象创建实例
         Object.assign(TargetClass.prototype, MyChild.prototype) // 合并其他原型上的属性
         TargetClass.prototype.constructor = TargetClass

         const targetInstance = new TargetClass()
         console.log('🚀 ~ This is a result of console.log ~ ✨: ', targetInstance);
    </script>
</body>
</html>
